package com.bestcem.xm.user.util.business;

import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.bestcem.xm.common.core.uitls.DateUtil;
import com.bestcem.xm.componet.redis.service.RedisService;
import com.bestcem.xm.user.constant.UserCacheKeyConstant;
import com.bestcem.xm.user.service.dto.user.AuthDTO;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 企业管理用户生成jwt util
 *
 * @author wangyunyun <yunyun.wang@idiaoyan.com>
 * @version v1.0
 * @date 2022/3/1 14:30
 */
@Component
@Slf4j
public class UserJwtTokenUtil {
    @Autowired
    private RedisService redisService;

    @Autowired
    private UserCacheUtil userCacheUtil;

    /**
     * jwt header parameter name
     */
    public static final String HEADER_KEY_TYPE = "typ";

    /**
     * jwt header parameter value
     */
    public static final String HEADER_KEY_VALUE = "JWT";

    /**
     * token前缀, 如果token有该前缀, 解析时需要去除
     */
    public static final String TOKEN_HEADER = "Bearer ";

    @Value("${jwt.secret-key:9C4E23F8-128D-475F-1111-587AFC8B18BC}")
    private String secret;
    @Value("${jwt.timeout:7200}")
    private Integer timeout;
    @Value("${jwt.refresh-timeout:43200}")
    private Integer refreshTimeout;
    @Value("${jwt.issuer:'IDY:MX'}")
    private String issuer;
    @Value("${jwt.not-before:true}")
    private Boolean notBefore;

    /**
     * 默认签名算法
     */
    private static final SignatureAlgorithm ALGORITHM = SignatureAlgorithm.HS256;

    /**
     * 单例模式下，默认的key，重启后才能重新生成
     */
    private SecretKey secretKey;

    /**
     * 生成密钥
     */
    @PostConstruct
    private void initKey() {
        setSecretKey(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), ALGORITHM.getJcaName()));
    }

    /**
     * secretKey set方法
     */
    private void setSecretKey(SecretKey key) {
        this.secretKey = key;
    }

    /**
     * secretKey get方法
     */
    private SecretKey getSecretKey() {
        return this.secretKey;
    }

    /**
     * 生成token
     *
     * @param authDTO
     * @return
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @version v1.0
     * @date 2022/3/1 14:30
     */
    public String generateToken(AuthDTO authDTO) {
        return generateTokenCommon(authDTO, false);
    }

    /**
     * 生成传送门12小时的token
     *
     * @param authDTO
     * @return
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @version v1.0
     * @date 2022/3/1 14:30
     */
    public String generateDeliveryToken(AuthDTO authDTO) {
        return generateTokenCommon(authDTO, true);
    }

    /**
     * 生成refreshToken
     *
     * @param orgCode
     * @param userId
     * @return
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @version v1.0
     * @date 2022/3/1 14:30
     */
    public String generateRefreshToken(String orgCode, String userId) {
        AuthDTO authDTO = AuthDTO.builder()
                .userId(userId)
                .orgCode(orgCode)
                .build();

        return generateTokenCommon(authDTO, true);
    }

    /**
     * 获取token信息
     *
     * @param token
     * @return
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @version v1.0
     * @date 2022/3/7 16:40
     */
    public AuthDTO parseToken(String token) {
        if (token.startsWith(TOKEN_HEADER)) {
            token = token.substring(TOKEN_HEADER.length());
        }
        // 从token中获取JWT中的信息
        Object body = Jwts.parserBuilder()
                .setSigningKey(getSecretKey())
                .build()
                .parse(token).getBody();
        return JSON.parseObject(JSON.toJSONString(body), AuthDTO.class);
    }

    /**
     * token格式化日期
     *
     * @param date
     * @return
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @version v1.0
     * @date 2022/3/1 14:30
     */
    public static String formatDate(Date date) {
        return new SimpleDateFormat(DateUtil.YYYY_MM_DD).format(date);
    }

    /**
     * 生成token的公共方法
     *
     * @param authDTO
     * @return
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @version v1.0
     * @date 2022/3/1 14:30
     */
    private String generateTokenCommon(AuthDTO authDTO, boolean isRefresh) {
        Date now = new Date();
        // 设置token版本
        setTokenVersion(authDTO);

        if (Boolean.TRUE.equals(notBefore)) {
            authDTO.setNbf(authDTO.getIat());
        }

        Map<String, Object> claims = JSON.parseObject(JSON.toJSONString(authDTO), new TypeReference<Map<String, Object>>() {
        });
        // python在header加了 "typ":"JWT" 的属性，不加会报错
        return Jwts.builder()
                .setHeaderParam(HEADER_KEY_TYPE, HEADER_KEY_VALUE)
                .setIssuedAt(now)
                .setIssuer(issuer)
                .setExpiration(generateExpirationDate(isRefresh, now))
                .addClaims(claims)
                .signWith(getSecretKey(), ALGORITHM)
                .compact();
    }

    /**
     * 生成token的过期时间
     */
    private Date generateExpirationDate(boolean isRefresh, Date now) {
        return isRefresh ? DateUtils.addSeconds(now, this.refreshTimeout) : DateUtils.addSeconds(now, this.timeout);
    }

    /**
     * 设置token的版本
     *
     * @param authDTO
     * @return
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @version v1.0
     * @date 2022/3/1 14:30
     */
    private void setTokenVersion(AuthDTO authDTO) {
        String tokenVersionStr = userCacheUtil.get(UserCacheKeyConstant.USER_TOKEN_VERSION, authDTO.getUserId());
        if (StringUtils.isNotBlank(tokenVersionStr)) {
            authDTO.setTokenVersion(Long.valueOf(tokenVersionStr));
        }
    }

    /**
     * 更新用户的token版本号
     *
     * @param userId 用户id
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/6/2 17:37
     */
    public void updateTokenVersion(String userId) {
        userCacheUtil.incrBy(UserCacheKeyConstant.USER_TOKEN_VERSION, userId);
    }

    /**
     * 校验token版本号
     *
     * @param authDTO
     * @return boolean 版本号校验通过返回true
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/8/12 14:57
     */
    public boolean checkTokenVersion(AuthDTO authDTO) {
        if (authDTO != null && StringUtils.isNotBlank(authDTO.getUserId())) {
            String tokenVersionStr = userCacheUtil.get(UserCacheKeyConstant.USER_TOKEN_VERSION, authDTO.getUserId());
            return (authDTO.getTokenVersion() == null && StringUtils.isBlank(tokenVersionStr))
                    || (authDTO.getTokenVersion() != null && StringUtils.isNotBlank(tokenVersionStr)
                    && String.valueOf(authDTO.getTokenVersion()).equals(tokenVersionStr));
        }
        return true;
    }

    /**
     * 将token加入jwt失效白名单中
     *
     * @param token
     * @return void
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/03/03 15:04
     */
    public void setJwtInvalid(String token) {
        token = SecureUtil.sha1(token);
        String key = MessageFormat.format(UserCacheKeyConstant.BLACK_TOKEN, token);
        redisService.set(key, "1", timeout, TimeUnit.SECONDS);
    }

    /**
     * 是否存储jwt失效黑名单中
     *
     * @param token
     * @return void
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/03/03 15:04
     */
    public Boolean existJwtInvalid(String token) {
        token = SecureUtil.sha1(token);
        String key = MessageFormat.format(UserCacheKeyConstant.BLACK_TOKEN, token);
        return redisService.hasKey(key);

    }

    /**
     * 生成定时任务的token
     *
     * @return
     */
    public String generateTaskToken(AuthDTO authDTO) {
        Date now = new Date();
        Date expireDate;
        // 设置超时时间
        String dateString = "2099-01-01 00:00:00";
        try {
            expireDate = new SimpleDateFormat("yyyy-MM-dd").parse(dateString);
            Map<String, Object> claims = JSON.parseObject(JSON.toJSONString(authDTO), new TypeReference<Map<String, Object>>() {
            });
            // python在header加了 "typ":"JWT" 的属性，不加会报错
            return Jwts.builder()
                    .setHeaderParam(HEADER_KEY_TYPE, HEADER_KEY_VALUE)
                    .setIssuedAt(now)
                    .setIssuer(issuer)
                    .setExpiration(expireDate)
                    .addClaims(claims)
                    .signWith(getSecretKey(), ALGORITHM)
                    .compact();
        } catch (ParseException e) {
            log.error("[Task] token生成失败 e={}", e);
        }
        return null;
    }
}
